Regressão Linear com Python

Autor: Ronisson Lucas Calmon da Conceição

1. Estimadores de MQO

Características dos estimadores de MQO:

  • Fácil aplicação
  • Simples interpretação e propriedades desejáveis

1.1 Regressão Linear Simples

Introdução

  • $\mathbf{y}$: representa o fenômeno/variável target que queremos analisar em função de uma variável explicativa $\mathbf{x}$ (ou mesmo conjunto de variáveis explicativas) e um termo de perturbação $\mathbf{u}$ (ou termo de erro): $$ y = f(x)+u $$
  • Vamos focar inicialmente no problema que busca analisar a relação entre duas variáveis descrita por um modelo de regressão linear simples: $$ y = \beta_0+\beta_1 x +u$$
    Note que: $\mathbf{y}$ é a variável dependente e $\mathbf{x}$ é a variável explicativa (que prova as variações em $y$).
    Exemplos
  • $\text{salário(w)} = \beta_0+\beta_1\text{educação}+u$: o parâmetro $\beta_1$ refere-se ao efeito do grau de escolaridade do indivíduo no salário.
  • $\text{taxa_crime(e)} = \beta_0+\beta_1\text{quantidade_polícia}+u$: o parâmetro $\beta_1$ refere-se a redução da taxa de criminalidade em função de um aumento na quantidade de força polícial.
  • $\text{vendas(s)} = \beta_0+\beta_1\text{temperatura}+u$: para algum tipo de negócio o aumento da temperatura pode ter um efeito positivo na quantidade de vendas.

Terminologia do modelo de regressão linear simples:

y x
Variável Dependente Variável Independente
Variável Explicada Variável Explicativa
Variável de Resposta Variáve de Controle
Variável Prevista Variável Previsora
Regressando Regressor

Ou ainda:

$\underbrace{y}_{\text{target}} = \underbrace{\beta_0+\beta_1}_{\text{coeficientes}} \underbrace{ x}_{\text{ input}} +\underbrace{u}_{\text{termo de erro}}$

O termo de erro $\mathbf{u}$ diz respeito a outros fatores que afetam $\mathbf{y}$ mas não estão explícitos na equação da regressão.

Relação linear entre $\mathbf{y}$ e $\mathbf{x}$: $\mathbf{x}$ altera a variável $\mathbf{y}$ linearmente. Uma implicação desta premissa seria que independentemente do valor inicial de $\mathbf{x}$ teremos sempre o mesmo impacto de desta variável sobre $\mathbf{y}$.
Lembre que a relação é linear nos parâmetros do modelo.

Considere um modelo populacional $y = \beta_0+\beta_1 x+u$ e uma amostra desta população $(x_i,y_i), i = 1, \dots , n$. Podemos obter que os estimadores de MQO para uma regressão linear simples:

$\min_{\hat{\beta}_0, \hat{\beta}_1} \sum_{i=1}^{n}\hat{u}_i^2 = \sum_{i=1}^{n}\left(y_i-\hat{\beta}_0-\hat{\beta}_1x_i\right)^2$

$\dfrac{\partial \sum_{i=1}^{n}\hat{u}_i^2}{\partial \hat{\beta}_0} = 0$

$\dfrac{\partial \sum_{i=1}^{n}\hat{u}_i^2}{\partial \hat{\beta}_0} = 0$

$$ \hat{\beta}_0 = \overline{y}-\hat{\beta}_1\overline{x}$$
$$\hat{\beta}_1 = \dfrac{\mathrm{cov(x,y)}}{\mathrm{var(x)}} = \dfrac{\sum_{i=1}^{n}\left(x_i-\overline{x}\right)\left(y_i-\overline{y}\right)}{\sum_{i=1}^{n}\left(x_i-\overline{x}\right)^2}$$

Modelo estimado:

$$\hat{y} = \hat{\beta}_0+\hat{\beta}_1 x$$

Resíduo do modelo:

$$\hat{u}_i = y_i-\hat{y}_i$$

Assim o resíduo da regressão é definido como a diferença entre o valor $y_i$ (valor real) e o valor previsto/ajustado $\hat{y}_i$. Se $\hat{u}_i>0 \Rightarrow$ então a reta subestima $y_i$; por outro lado, se $\hat{u}_i<0$ então a reta superestima $y_i$. O mundo ideal seria $\hat{u}_i = 0$, mas na prática teremos para a maior parte dos casos $\hat{u}_i\neq 0$.

Hands On!

Nosso objetivo é estimar um modelo para explicar o salário com base no tempo de experiência.

In [1]:
import pandas as pd
import numpy as np
import statsmodels.formula.api as smf
import seaborn as sns
import matplotlib.pyplot as plt
import plotly.graph_objects as go
import warnings
warnings.filterwarnings("ignore")
In [2]:
df = pd.read_csv('data.csv')
df.head()
Out[2]:
tempo_experiencia salario
0 1.1 39343.0
1 1.3 46205.0
2 1.5 37731.0
3 2.0 43525.0
4 2.2 39891.0
In [3]:
df.describe()
Out[3]:
tempo_experiencia salario
count 30.000000 30.000000
mean 5.313333 76003.000000
std 2.837888 27414.429785
min 1.100000 37731.000000
25% 3.200000 56720.750000
50% 4.700000 65237.000000
75% 7.700000 100544.750000
max 10.500000 122391.000000
In [4]:
sns.pairplot(df);
In [5]:
# correlação linear de Pearson
df.corr()
Out[5]:
tempo_experiencia salario
tempo_experiencia 1.000000 0.978242
salario 0.978242 1.000000
In [6]:
figure = go.Figure(data = go.Scatter(x = df['tempo_experiencia'], 
                                     y = df['salario'],
                                     mode = 'markers'
                                    ),
                   layout = go.Layout(xaxis = dict(title = 'Tempo de experiência'),
                                      yaxis = dict(title = 'Salário'),
                                      title = 'Relação entre salário e tempo de experiência'
                                     )
                  )

figure.update_yaxes(tickprefix = '$')
figure

Nosso objetivo é então obter uma reta que melhor se ajuste aos dados.

In [7]:
modelo = smf.ols(formula = 'salario ~ tempo_experiencia', data = df).fit()
In [8]:
modelo.params
Out[8]:
Intercept            25792.200199
tempo_experiencia     9449.962321
dtype: float64
In [9]:
# previsão
fitted_values = modelo.fittedvalues
In [10]:
figure = go.Figure(
    data = [
        go.Scatter(
            x = df['tempo_experiencia'],
            y = fitted_values,
            name = 'Reta ajustada',
            mode = 'lines',
            line = dict(color = 'black')
        ),
        go.Scatter(
            x = df['tempo_experiencia'],
            y = df['salario'],
            mode = 'markers',
            name = 'Amostra'
        )
    ],
    layout = go.Layout(
        xaxis = dict(title = 'Tempo de experiência'),
        yaxis = dict(title = 'Salário'),
        title = 'OLS: salário ~ tempo de experiência'
    )
                  )

figure.update_yaxes(tickprefix = '$')
figure

Regressão linear múltipla

Na prática teremos mais de uma variável explicativa e podemos então generalizar para um modelo com $K$ variáveis explicativas e incorporar mais mais fatores que podem explicar $y$.

$$y = \beta_0+\beta_1x_1+\beta_2x_2+\beta_3x_3+\dots+\beta_Kx_k+u$$

Nota: $\beta_0$ é o intercepto do modelo, $\beta_1$ é o parâmetro associado a $x_1$, $\beta_2$ é o parâmetro associado a variável $x_2$, e assim sucessivamente.

Podemos usar notação matricial:

$$ \mathbf {y} = \mathbf {X\beta}+\mathbf{u}$$

Sendo que:

$ \mathbf {y} = \left[ \begin{array}{c c c} y_{1} \\ y_{2} \\ \vdots\\ y_{n}\\ \end{array}\right] $, $ \mathbf {\mathbf{\beta}} = \left[ \begin{array}{c c c} \beta_{0} \\ \beta_{1} \\ \vdots\\ \beta_{k}\\ \end{array}\right] $, $ \mathbf {u} = \left[ \begin{array}{c c c} u_{0} \\ u_{1} \\ \vdots\\ u_{n}\\ \end{array}\right] $, $ \mathbf {X} = \left[ \begin{array}{c c c} 1 & x_{11}& \ldots& x_{1k}\\ 1 & x_{21}& \ldots& x_{2k}\\ \vdots & \vdots & \ddots & \vdots\\ 1 & x_{n1} &\ldots & x_{nk} \end{array}\right] $

Nosso problema de otimização e obtenção dos parâmetros consiste em:

$$\min_{\hat{\beta}}\mathbf{\hat{u'}\hat{u}} = \left(\mathbf{y}-\mathbf{X\hat{\beta}}\right)'\left(\mathbf{y}-\mathbf{X\hat{\beta}}\right)$$

Que resolvendo obtemos:

$$\therefore \mathbf {\hat\beta} = (\mathbf {X^{'}}\mathbf {X})^{-1} \mathbf {X^{'}}y $$
In [11]:
import statsmodels.api as sm
from sklearn.model_selection import train_test_split
import statsmodels.stats.api as sms
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
In [12]:
def line_plot(xaxis, yaxis, data_frame, title = '', 
              figsize = (12, 6),
              xlabel = '',
              ylabel = '',
              line_color = 'k'
             ):
    fig, ax = plt.subplots(figsize = figsize)
    ax.plot(data_frame[xaxis], data_frame[yaxis],
            color = line_color
           )
    ax.set(title = title, xlabel = xlabel, ylabel = ylabel)
In [13]:
def heatmap_plot(data_frame,
                 correlation_type = 'pearson',
                 title = '',
                 annot = True,
                 cmap = 'Blues',
                 figsize = (8, 8)
                ):
    corr = data_frame.corr(method = correlation_type)
    fig, ax = plt.subplots(figsize = figsize)
    sns.heatmap(corr, ax = ax,
                annot = annot,
                cmap = cmap
               )
    ax.set(title = title)
In [14]:
def count_plot(data, labelsize = 12,
               xlabel = '', 
               ylabel = 'Frequência',
               style = 'white',
              ):
    sns.set(rc = {
        'figure.figsize':(10, 5),
        'xtick.labelsize':labelsize,
        'ytick.labelsize':labelsize,
    })
    sns.set_style(style)
    count_plot = sns.countplot(x = data)
    count_plot.set_title('Countplot de '+data.name, fontsize = 15)
    count_plot.set(xlabel = xlabel,
                   ylabel = ylabel
                  )
In [15]:
#def distribution_plotter(data, label, bins=None):    
def distribution_plotter(data, label, bin_width=200):    
    sns.set(rc={"figure.figsize": (10, 7)})
    sns.set_style("white")    
    #dist = sns.distplot(data, bins= bins, hist_kws={'alpha':0.2}, kde_kws={'linewidth':5})
    dist = sns.histplot(data, 
                        stat = 'density', kde = True, 
                        line_kws = {'linewidth':5}, 
                        binwidth = bin_width)        
    dist.set_title('Distribuição de ' + label + '\n', fontsize=16)  

Hands On!

In [16]:
df = pd.read_csv('Consumo_cerveja.csv')
In [17]:
df.head()
Out[17]:
Data Temperatura Media (C) Temperatura Minima (C) Temperatura Maxima (C) Precipitacao (mm) Final de Semana Consumo de cerveja (litros)
0 2015-01-01 27,3 23,9 32,5 0 0.0 25.461
1 2015-01-02 27,02 24,5 33,5 0 0.0 28.972
2 2015-01-03 24,82 22,4 29,9 0 1.0 30.814
3 2015-01-04 23,98 21,5 28,6 1,2 1.0 29.799
4 2015-01-05 23,82 21 28,3 0 0.0 28.900
In [18]:
df.shape
Out[18]:
(941, 7)
In [19]:
df.drop_duplicates(inplace = True, keep = False)
In [20]:
df.shape
Out[20]:
(365, 7)
In [21]:
df.isnull().sum()
Out[21]:
Data                           0
Temperatura Media (C)          0
Temperatura Minima (C)         0
Temperatura Maxima (C)         0
Precipitacao (mm)              0
Final de Semana                0
Consumo de cerveja (litros)    0
dtype: int64
In [22]:
df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 365 entries, 0 to 364
Data columns (total 7 columns):
 #   Column                       Non-Null Count  Dtype  
---  ------                       --------------  -----  
 0   Data                         365 non-null    object 
 1   Temperatura Media (C)        365 non-null    object 
 2   Temperatura Minima (C)       365 non-null    object 
 3   Temperatura Maxima (C)       365 non-null    object 
 4   Precipitacao (mm)            365 non-null    object 
 5   Final de Semana              365 non-null    float64
 6   Consumo de cerveja (litros)  365 non-null    float64
dtypes: float64(2), object(5)
memory usage: 22.8+ KB
In [23]:
df.dtypes
Out[23]:
Data                            object
Temperatura Media (C)           object
Temperatura Minima (C)          object
Temperatura Maxima (C)          object
Precipitacao (mm)               object
Final de Semana                float64
Consumo de cerveja (litros)    float64
dtype: object
In [24]:
for column in df.select_dtypes(include = object).columns[1:]:
    df[column] = pd.to_numeric(df[column].str.replace(',','.'))
In [25]:
df['Data'] = pd.to_datetime(df['Data'], format = '%Y-%m-%d')
In [26]:
df.columns
Out[26]:
Index(['Data', 'Temperatura Media (C)', 'Temperatura Minima (C)',
       'Temperatura Maxima (C)', 'Precipitacao (mm)', 'Final de Semana',
       'Consumo de cerveja (litros)'],
      dtype='object')
In [27]:
df.rename(
    columns = {
        'Temperatura Media (C)': 'Temperatura_mean',
        'Temperatura Maxima (C)': 'Temperatura_Max',
        'Temperatura Minima (C)': 'Temperatura_min',
        'Precipitacao (mm)': 'Precipitacao',
        'Final de Semana': 'Final_semana',
        'Consumo de cerveja (litros)': 'Consumo_cerveja'
    },
    inplace = True
)
In [28]:
df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 365 entries, 0 to 364
Data columns (total 7 columns):
 #   Column            Non-Null Count  Dtype         
---  ------            --------------  -----         
 0   Data              365 non-null    datetime64[ns]
 1   Temperatura_mean  365 non-null    float64       
 2   Temperatura_min   365 non-null    float64       
 3   Temperatura_Max   365 non-null    float64       
 4   Precipitacao      365 non-null    float64       
 5   Final_semana      365 non-null    float64       
 6   Consumo_cerveja   365 non-null    float64       
dtypes: datetime64[ns](1), float64(6)
memory usage: 22.8 KB
In [29]:
df.describe()
Out[29]:
Temperatura_mean Temperatura_min Temperatura_Max Precipitacao Final_semana Consumo_cerveja
count 365.000000 365.000000 365.000000 365.000000 365.000000 365.000000
mean 21.226356 17.461370 26.611507 5.196712 0.284932 25.401367
std 3.180108 2.826185 4.317366 12.417844 0.452001 4.399143
min 12.900000 10.600000 14.500000 0.000000 0.000000 14.343000
25% 19.020000 15.300000 23.800000 0.000000 0.000000 22.008000
50% 21.380000 17.900000 26.900000 0.000000 0.000000 24.867000
75% 23.280000 19.600000 29.400000 3.200000 1.000000 28.631000
max 28.860000 24.500000 36.500000 94.800000 1.000000 37.937000
In [30]:
cols_temperatura = df.iloc[:,1:4]
In [31]:
fig, ax = plt.subplots(figsize = (12, 6))
for column in cols_temperatura:
    ax.plot(df['Data'], df[column])
ax.set(title = 'Temperaturas máxima, média e mínima',
       xlabel = 'Data'
      );
In [32]:
traces = list()
for column in cols_temperatura:
    trace = go.Scatter(
        x =  df['Data'],
        y = df[column],
        name = column,
        mode = 'lines'
    )
    traces.append(trace)
    
layout = go.Layout(
    xaxis = dict(title = 'Data'),
    yaxis = dict(title = 'Temperatura'),
    title = 'Temperaturas máxima, média e mínima'
)

fig = go.Figure(data = traces, layout = layout)
fig
In [33]:
line_plot('Data', 'Precipitacao', df, 'Precipitação em mm')

Vamos agora entender o consumo de cerveja:

In [34]:
line_plot('Data', 'Consumo_cerveja', df, title = 'Consumo de cerveja em 2015')
In [35]:
corr = df.corr()
corr
Out[35]:
Temperatura_mean Temperatura_min Temperatura_Max Precipitacao Final_semana Consumo_cerveja
Temperatura_mean 1.000000 0.862752 0.922513 0.024416 -0.050803 0.574615
Temperatura_min 0.862752 1.000000 0.672929 0.098625 -0.059534 0.392509
Temperatura_Max 0.922513 0.672929 1.000000 -0.049305 -0.040258 0.642672
Precipitacao 0.024416 0.098625 -0.049305 1.000000 0.001587 -0.193784
Final_semana -0.050803 -0.059534 -0.040258 0.001587 1.000000 0.505981
Consumo_cerveja 0.574615 0.392509 0.642672 -0.193784 0.505981 1.000000
In [36]:
# matriz de correlação de Pearson
correlation_matrix = df.corr()
mask = np.zeros_like(correlation_matrix)
mask[np.triu_indices_from(mask)] = True
sns.heatmap(
    correlation_matrix,
    annot = True,
    mask = mask,
    square = True,
    cmap = 'Blues');
In [37]:
df.iloc[:,1:].plot(kind = 'box', figsize = (12, 5));
In [38]:
df.iloc[:,1:].hist(figsize = (12, 8), bins = 30);
In [39]:
sns.pairplot(df);

Criando o modelo

In [40]:
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
In [41]:
X = df.drop(columns = ['Data', 'Consumo_cerveja'])
y = df['Consumo_cerveja']
In [42]:
X_train, X_test, y_train, y_test = train_test_split(X, y, 
                                                    test_size = 0.3, 
                                                    random_state = 3)
In [43]:
X_train.shape, y_train.shape, X_test.shape, y_test.shape
Out[43]:
((255, 5), (255,), (110, 5), (110,))
In [44]:
linear_model = LinearRegression()
In [45]:
linear_model.fit(X, y)
Out[45]:
LinearRegression()

Coeficientes estimados:

In [46]:
predict = linear_model.predict(X_test)

Score:

In [47]:
linear_model.score(X_test, y_test)
Out[47]:
0.7569341734823147
In [48]:
mean_squared_error(y_true = y_test, y_pred = predict)
Out[48]:
4.712576083357044

Usando statsmodels

In [49]:
import statsmodels.formula.api as smf

Vamos estimar o seguinte modelo:

$$ \text{consumo_cerveja} = \beta_0 + \beta_1\text{temperatura_mean}+ \beta_2\text{temperatura_min}+\beta_3\text{temperatura_max}+\beta_4\text{final_semana}+\beta_5\text{precipitação} $$
In [50]:
# modelo com constante
model_1 = smf.ols(
formula = """
Consumo_cerveja~Temperatura_mean+Temperatura_min+Temperatura_Max+Precipitacao+Final_semana
""",
data = df
).fit()
In [51]:
model_1.summary(title = 'Modelo 1')
Out[51]:
Modelo 1
Dep. Variable: Consumo_cerveja R-squared: 0.723
Model: OLS Adj. R-squared: 0.719
Method: Least Squares F-statistic: 187.1
Date: Tue, 12 Oct 2021 Prob (F-statistic): 1.19e-97
Time: 19:27:12 Log-Likelihood: -824.07
No. Observations: 365 AIC: 1660.
Df Residuals: 359 BIC: 1684.
Df Model: 5
Covariance Type: nonrobust
coef std err t P>|t| [0.025 0.975]
Intercept 6.4447 0.845 7.627 0.000 4.783 8.107
Temperatura_mean 0.0308 0.188 0.164 0.870 -0.339 0.401
Temperatura_min -0.0190 0.110 -0.172 0.863 -0.236 0.198
Temperatura_Max 0.6560 0.095 6.895 0.000 0.469 0.843
Precipitacao -0.0575 0.010 -5.726 0.000 -0.077 -0.038
Final_semana 5.1832 0.271 19.126 0.000 4.650 5.716
Omnibus: 39.362 Durbin-Watson: 1.930
Prob(Omnibus): 0.000 Jarque-Bera (JB): 12.936
Skew: 0.153 Prob(JB): 0.00155
Kurtosis: 2.130 Cond. No. 271.


Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

Agora vamos estimar o seguinte modelo:

$$ \text{consumo_cerveja} =\beta_1\text{temperatura_mean}+ \beta_2\text{temperatura_min}+\beta_3\text{temperatura_max}+\beta_4\text{final_semana}$$
In [52]:
model_2 = smf.ols(
formula = """
Consumo_cerveja~0+Temperatura_mean+Temperatura_min+Temperatura_Max+Precipitacao+Final_semana
""",
data = df
).fit()
In [53]:
model_2.summary(title='Modelo 2')
Out[53]:
Modelo 2
Dep. Variable: Consumo_cerveja R-squared (uncentered): 0.991
Model: OLS Adj. R-squared (uncentered): 0.991
Method: Least Squares F-statistic: 7620.
Date: Tue, 12 Oct 2021 Prob (F-statistic): 0.00
Time: 19:27:12 Log-Likelihood: -851.48
No. Observations: 365 AIC: 1713.
Df Residuals: 360 BIC: 1732.
Df Model: 5
Covariance Type: nonrobust
coef std err t P>|t| [0.025 0.975]
Temperatura_mean 0.1192 0.202 0.590 0.555 -0.278 0.516
Temperatura_min 0.1146 0.117 0.977 0.329 -0.116 0.345
Temperatura_Max 0.7313 0.102 7.179 0.000 0.531 0.932
Precipitacao -0.0552 0.011 -5.112 0.000 -0.076 -0.034
Final_semana 5.4816 0.289 18.989 0.000 4.914 6.049
Omnibus: 20.752 Durbin-Watson: 1.721
Prob(Omnibus): 0.000 Jarque-Bera (JB): 9.729
Skew: -0.175 Prob(JB): 0.00771
Kurtosis: 2.281 Cond. No. 85.8


Notes:
[1] R² is computed without centering (uncentered) since the model does not contain a constant.
[2] Standard Errors assume that the covariance matrix of the errors is correctly specified.
  • Os parâmetros do modelo são significativos.
  • Log-Likelihood: usamos para fazer comparação entre modelos; varia de $-\infty,+\infty$ e quanto maior o valor, melhor o modelo. O modelo 1 é melhor que o 2 sob essa regra.
  • AIC e BIC são utilizados para comparar modelos, com fundamentação estatística mais rigorosa que o $R^2$ ajustado. O modelo com menor critério de informação é o melhor.
  • Teste Omnibus: normalidade da distribuição dos resíduos do modelo. 0 indica normalidade perfeita. Prob(Omnibus) mensura a probabilidade de os resíduos serem normalmente distribuídos. 1 indica distribuição perfeitamente normal.
  • Skew: mede o grau de simetria (assimetria) da distribuição dos resíduos. Se a distribuição for normal a assimetria é nula. Se a distribuição for assimétrica positiva (negativa) os valores são positivos (negativos).
  • Curtose: mede o grau de achatamento da distribuição de probabilidade.
  • Os resíduos do modelo são normalmente distribuídos?
  • Teste de autocorrelação DW:
    • Valor próximo de 4: há evidência de autocorrelação negativa;
    • Valor próximo de 2: há evidência de ausência de autocorrelação;
    • Valor próximo de 0: há evidência de autocorrelação positiva.
  • Teste F: Teste de significância conjunta dos parâmetros.
    • $H_0: \beta_1=\beta_2=\beta_3=\dots=\beta_k=0$
    • $H_1: \beta_k \neq 0 $
  • Teste t: testa de significância individual dos parâmaetros do modelo
    • $H_0: \beta_j = 0$
    • $H_1: \beta_j \neq 0$
    • Se $\beta_j = 0$ então não há relação linear entre a variável explicativa e a variável dependente.
In [54]:
predicoes = pd.DataFrame(model_1.predict(), columns=['Predições 1'])
predicoes['Predições 2'] = model_2.predict()
predicoes['Consumo de cerveja (litros)']= df['Consumo_cerveja']
In [55]:
predicoes.plot();

Referências

  1. BUSSAB, W. O.; MORETTIN, P. A. Estatística Básica. 6. ed. São Paulo : Saraiva, 2010.
  2. SARTORIS, A. Estatística e introdução à econometria. São Paulo: Saraiva, 2003.
  3. Regressão Linear: https://ivanildo-batista13.medium.com/regress%C3%A3o-linear-m%C3%BAltipla-em-python-eb4b6603a3